package io.gitee.hefren.hhcache.core;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

import java.util.*;
import java.util.stream.Stream;

/**
 * @Date 2024/7/3
 * @Author lifei
 */
public class HhCache {

    private Map<String, CacheEntry<?>> map = Maps.newHashMap();

    //====== 1. string begin ===

    public void set(String key, String value) {
        map.put(key, new CacheEntry<>(value));
    }

    public String get(String key) {
        CacheEntry<String> entry = (CacheEntry<String>) map.get(key);
        return Objects.isNull(entry)? null: entry.getValue();
    }

    public int strlen(String key) {
        return Objects.isNull(get(key))? 0 : get(key).length();
    }

    public int exists(String... keys) {
        if (Objects.isNull(keys)) {
            return 0;
        }
        return (int)Arrays.stream(keys).filter(map::containsKey).count();
    }

    public int del(String... keys) {
        if (Objects.isNull(keys)) {
            return 0;
        }
        return (int)Arrays.stream(keys).map(map::remove).filter(Objects::nonNull).count();
    }

    public void mset(String[] keys, String[] values) {
        if (Objects.isNull(keys)) {
            return;
        }
        for (int i = 0; i < keys.length; i++) {
            this.set(keys[i], values[i]);
        }
    }

    public String[] mget(String... keys) {
        if (Objects.isNull(keys)) {
            return null;
        }
        return Arrays.stream(keys).map(this::get).toArray(String[]::new);
    }

    public int incr(String key) {
        try {
            map.putIfAbsent(key, new CacheEntry<>("0"));
            int result = Integer.parseInt(this.get(key)) + 1;
            this.set(key, String.valueOf(result));
            return result;
        }catch (NumberFormatException e) {
            throw e;
        }
    }

    public int decr(String key) {
        try {
            map.putIfAbsent(key, new CacheEntry<>("0"));
            int result = Integer.parseInt(this.get(key)) - 1;
            this.set(key, String.valueOf(result));
            return result;
        }catch (NumberFormatException e) {
            throw e;
        }
    }

    //====== 1. string end ===
    //====== 2. list begin ===

    public int lpush(String key, String... values) {
        map.putIfAbsent(key, new CacheEntry<LinkedList<String>>(Lists.newLinkedList()));
        CacheEntry<LinkedList<String>> entry = (CacheEntry<LinkedList<String>>) map.get(key);
        LinkedList<String> list = entry.getValue();
        if (Objects.isNull(values)) {
            return list.size();
        }
        for (String value : values) {
            list.addFirst(value);
        }
        return list.size();
    }

    public int rpush(String key, String[] values) {
        map.putIfAbsent(key, new CacheEntry<LinkedList<String>>(Lists.newLinkedList()));
        CacheEntry<LinkedList<String>> entry = (CacheEntry<LinkedList<String>>) map.get(key);
        LinkedList<String> list = entry.getValue();
        if (Objects.isNull(values)) {
            return list.size();
        }
        for (String value : values) {
            list.addLast(value);
        }
        return list.size();
    }

    public String[] lpop(String key, int num) {
        CacheEntry<LinkedList<String>> entry = (CacheEntry<LinkedList<String>>) map.get(key);
        if (Objects.isNull(entry) || Objects.isNull(entry.getValue()) || entry.getValue().isEmpty()) {
            return null;
        }
        LinkedList<String> list = entry.getValue();
        int len = Math.min(num, list.size());
        String[] result = new String[len];
        for (int i = 0; i < len; i++) {
           result[i] = list.removeFirst();
        }
        return result;
    }

    public String[] rpop(String key, int num) {
        CacheEntry<LinkedList<String>> entry = (CacheEntry<LinkedList<String>>) map.get(key);
        if (Objects.isNull(entry) || Objects.isNull(entry.getValue()) || entry.getValue().isEmpty()) {
            return null;
        }
        LinkedList<String> list = entry.getValue();
        int len = Math.min(num, list.size());
        String[] result = new String[len];
        for (int i = 0; i < len; i++) {
            result[i] = list.removeLast();
        }
        return result;
    }

    public int llen(String key) {
        CacheEntry<LinkedList<String>> entry = (CacheEntry<LinkedList<String>>) map.get(key);
        if (Objects.isNull(entry) || Objects.isNull(entry.getValue()) || entry.getValue().isEmpty()) {
            return 0;
        }
        return entry.getValue().size();
    }

    public String lindex(String key, int index) {
        CacheEntry<LinkedList<String>> entry = (CacheEntry<LinkedList<String>>) map.get(key);
        if (Objects.isNull(entry) || Objects.isNull(entry.getValue()) || entry.getValue().isEmpty()) {
            return null;
        }
        LinkedList<String> list = entry.getValue();
        if (index<0 || index >= list.size()) {
            return null;
        }
        return list.get(index);
    }

    public String[] lrange(String key, int begin, int end) {
        CacheEntry<LinkedList<String>> entry = (CacheEntry<LinkedList<String>>) map.get(key);
        if (Objects.isNull(entry) || Objects.isNull(entry.getValue()) || entry.getValue().isEmpty()) {
            return null;
        }
        LinkedList<String> list = entry.getValue();
        if (begin>end || begin > list.size()) {
            return null;
        }
        int beginIndex = Math.max(begin, 0);
        int endIndex = Math.min(end, list.size()-1);
        int len = endIndex - beginIndex + 1;
        String[] result = new String[len];
        for (int i = 0; i < len; i++) {
            result[i] = list.get(beginIndex + i);
        }
        return result;
    }



    //====== 2. list end ===
    //====== 3. set begin ===

    public int sadd(String key, String[] values) {
        map.putIfAbsent(key, new CacheEntry<LinkedHashSet<String>>(new LinkedHashSet<>()));
        CacheEntry<LinkedHashSet<String>> entry = (CacheEntry<LinkedHashSet<String>>) map.get(key);
        int num = 0;
        if (Objects.isNull(values)) {
            return num;
        }
        LinkedHashSet<String> currentSet = entry.getValue();
        for (String value : values) {
            if (!currentSet.contains(value)) {
                num ++;
                currentSet.add(value);
            }
        }
        return num;
    }

    public String[] smembers(String key) {
        CacheEntry<LinkedHashSet<String>> entry = (CacheEntry<LinkedHashSet<String>>) map.get(key);
        if (Objects.isNull(entry) || Objects.isNull(entry.getValue()) || entry.getValue().isEmpty()) {
            return null;
        }
        LinkedHashSet<String> currentSet = entry.getValue();
        return currentSet.toArray(String[]::new);
    }

    public int sismember(String key, String value) {
        CacheEntry<LinkedHashSet<String>> entry = (CacheEntry<LinkedHashSet<String>>) map.get(key);
        if (Objects.isNull(entry) || Objects.isNull(entry.getValue()) || entry.getValue().isEmpty()) {
            return 0;
        }
        LinkedHashSet<String> currentSet = entry.getValue();
        return currentSet.contains(value)? 1: 0;
    }

    public int scard(String key) {
        CacheEntry<LinkedHashSet<String>> entry = (CacheEntry<LinkedHashSet<String>>) map.get(key);
        if (Objects.isNull(entry) || Objects.isNull(entry.getValue()) || entry.getValue().isEmpty()) {
            return 0;
        }
        LinkedHashSet<String> currentSet = entry.getValue();
        return currentSet.size();
    }

    public int srem(String key, String[] values) {
        CacheEntry<LinkedHashSet<String>> entry = (CacheEntry<LinkedHashSet<String>>) map.get(key);
        if (Objects.isNull(entry) || Objects.isNull(entry.getValue()) || entry.getValue().isEmpty()) {
            return 0;
        }
        if (Objects.isNull(values)) {
            return 0;
        }
        LinkedHashSet<String> currentSet = entry.getValue();
        return (int) Arrays.stream(values).filter(currentSet::remove).count();
    }

    private static final Random RANDOM = new Random(System.currentTimeMillis());

    public String[] spop(String key, int num) {
        CacheEntry<LinkedHashSet<String>> entry = (CacheEntry<LinkedHashSet<String>>) map.get(key);
        if (Objects.isNull(entry) || Objects.isNull(entry.getValue()) || entry.getValue().isEmpty()) {
            return null;
        }
        LinkedHashSet<String> currentSet = entry.getValue();
        int len = Math.min(num, currentSet.size());
        String[] result = new String[len];
        for (int i = 0; i < len; i++) {
            String[] currentArray = currentSet.toArray(String[]::new);
            String v = currentArray[RANDOM.nextInt(currentArray.length)];
            result[i] = v;
            currentSet.remove(v);
        }
        return result;
    }




    //====== 3. set end ===
    //====== 4. hash begin ===
    public int hset(String key, String[] fields, String[] values) {
        map.putIfAbsent(key, new CacheEntry<HashMap<String, String>>(new HashMap<>()));
        CacheEntry<HashMap<String, String>> entry = (CacheEntry<HashMap<String, String>>) map.get(key);
        int num = 0;
        if (Objects.isNull(values)) {
            return num;
        }
        HashMap<String, String> currentMap = entry.getValue();
        for (int i = 0; i < fields.length; i++) {
            if (!currentMap.containsKey(fields[i])) {
                num ++;
            }
            currentMap.put(fields[i], values[i]);
        }
        return num;
    }

    public String hget(String key, String field) {
        CacheEntry<HashMap<String, String>> entry = (CacheEntry<HashMap<String, String>>) map.get(key);
        if (Objects.isNull(entry) || Objects.isNull(entry.getValue()) || entry.getValue().isEmpty()) {
            return null;
        }
        HashMap<String, String> currentMap = entry.getValue();
        return currentMap.get(field);
    }

    public String[] hgetall(String key) {
        CacheEntry<HashMap<String, String>> entry = (CacheEntry<HashMap<String, String>>) map.get(key);
        if (Objects.isNull(entry) || Objects.isNull(entry.getValue()) || entry.getValue().isEmpty()) {
            return null;
        }
        HashMap<String, String> currentMap = entry.getValue();
        return currentMap.entrySet().stream().flatMap(itemEntry-> Stream.of(itemEntry.getKey(), itemEntry.getValue())).toArray(String[]::new);
    }

    public String[] hmget(String key, String[] fields) {
        CacheEntry<HashMap<String, String>> entry = (CacheEntry<HashMap<String, String>>) map.get(key);
        if (Objects.isNull(entry) || Objects.isNull(entry.getValue()) || entry.getValue().isEmpty()) {
            return null;
        }
        if (Objects.isNull(fields)) {
            return null;
        }
        HashMap<String, String> currentMap = entry.getValue();
        return Arrays.stream(fields).map(currentMap::get).toArray(String[]::new);
    }

    public int hlen(String key) {
        CacheEntry<HashMap<String, String>> entry = (CacheEntry<HashMap<String, String>>) map.get(key);
        if (Objects.isNull(entry) || Objects.isNull(entry.getValue()) || entry.getValue().isEmpty()) {
            return 0;
        }
        HashMap<String, String> currentMap = entry.getValue();
        return currentMap.size();
    }

    public int hdel(String key, String[] fields) {
        CacheEntry<HashMap<String, String>> entry = (CacheEntry<HashMap<String, String>>) map.get(key);
        if (Objects.isNull(entry) || Objects.isNull(entry.getValue()) || entry.getValue().isEmpty()) {
            return 0;
        }
        if (Objects.isNull(fields)) {
            return 0;
        }
        HashMap<String, String> currentMap = entry.getValue();
        return (int) Arrays.stream(fields).map(currentMap::remove).filter(Objects::nonNull).count();
    }

    public int hexists(String key, String field) {
        CacheEntry<HashMap<String, String>> entry = (CacheEntry<HashMap<String, String>>) map.get(key);
        if (Objects.isNull(entry) || Objects.isNull(entry.getValue()) || entry.getValue().isEmpty()) {
            return 0;
        }
        HashMap<String, String> currentMap = entry.getValue();
        return currentMap.containsKey(field)? 1: 0;
    }

    //====== 4. hash end ===

    //====== 5. zset begin ===

    public int zadd(String key, double[] scores, String[] values) {
        map.putIfAbsent(key, new CacheEntry<LinkedHashSet<ZsetEntry>>(new LinkedHashSet<>()));
        CacheEntry<LinkedHashSet<ZsetEntry>> entry = (CacheEntry<LinkedHashSet<ZsetEntry>>) map.get(key);
        int num = 0;
        if (Objects.isNull(values) || values.length==0) {
            return num;
        }
        LinkedHashSet<ZsetEntry> currentSet = entry.getValue();
        for (int i = 0; i < values.length; i++) {
            ZsetEntry zsetEntry = new ZsetEntry(scores[i], values[i]);
            if (!currentSet.contains(zsetEntry)) {
                num ++;
            } else {
                currentSet.remove(zsetEntry);
            }
            currentSet.add(zsetEntry);
        }
        return num;
    }

    public int zcard(String key) {
        CacheEntry<LinkedHashSet<ZsetEntry>> entry = (CacheEntry<LinkedHashSet<ZsetEntry>>) map.get(key);
        if (Objects.isNull(entry) || Objects.isNull(entry.getValue()) || entry.getValue().isEmpty()) {
            return 0;
        }
        LinkedHashSet<ZsetEntry> currentSet = entry.getValue();
        return currentSet.size();
    }

    public int zcount(String key, double min, double max) {
        CacheEntry<LinkedHashSet<ZsetEntry>> entry = (CacheEntry<LinkedHashSet<ZsetEntry>>) map.get(key);
        if (Objects.isNull(entry) || Objects.isNull(entry.getValue()) || entry.getValue().isEmpty()) {
            return 0;
        }
        LinkedHashSet<ZsetEntry> currentSet = entry.getValue();
        return (int) currentSet.stream().filter(item->item.getScore()>=min && item.getScore()<=max).count();
    }

    public Double zscore(String key, String value) {
        CacheEntry<LinkedHashSet<ZsetEntry>> entry = (CacheEntry<LinkedHashSet<ZsetEntry>>) map.get(key);
        if (Objects.isNull(entry) || Objects.isNull(entry.getValue()) || entry.getValue().isEmpty()) {
            return null;
        }
        LinkedHashSet<ZsetEntry> currentSet = entry.getValue();
        Optional<ZsetEntry> any = currentSet.stream().filter(item -> item.getValue().equals(value)).findAny();
        if (any.isPresent()) {
            return any.get().getScore();
        } else {
            return null;
        }
    }

    public Integer zrank(String key, String value) {
        CacheEntry<LinkedHashSet<ZsetEntry>> entry = (CacheEntry<LinkedHashSet<ZsetEntry>>) map.get(key);
        if (Objects.isNull(entry) || Objects.isNull(entry.getValue()) || entry.getValue().isEmpty()) {
            return null;
        }
        LinkedHashSet<ZsetEntry> currentSet = entry.getValue();
        ZsetEntry mock = new ZsetEntry(Double.MIN_VALUE, value);
        if (!currentSet.contains(mock)) {
            return null;
        }
        Double zscore = zscore(key, value);
        ZsetEntry zsetEntry = new ZsetEntry(zscore, value);
        return (int) currentSet.stream().filter(item->item.compareTo(zsetEntry)<0).count();
    }

    public int zrem(String key, String[] values) {
        CacheEntry<LinkedHashSet<ZsetEntry>> entry = (CacheEntry<LinkedHashSet<ZsetEntry>>) map.get(key);
        if (Objects.isNull(entry) || Objects.isNull(entry.getValue()) || entry.getValue().isEmpty()) {
            return 0;
        }
        LinkedHashSet<ZsetEntry> currentSet = entry.getValue();
        if (Objects.isNull(values) || values.length==0) {
            return 0;
        }
        return (int) Arrays.stream(values).map(item->new ZsetEntry(Double.MIN_VALUE, item)).filter(currentSet::remove).count();
    }
    //====== 5. zset end ===
}
